//
//  LocationCollectionViewCell.swift
//  SwiftWeather
//
//  Created by shen on 2023/8/28.
//

import Foundation
import Combine

class LocationCollectionViewCellViewModel {
    // MARK: Properties

    @Published private var city: City
    @Published private var weather: Weather?
    @Published private var temperatureUnits: TemperatureUnits
    @Published private var isWeatherRecieved = false

    var locationNamePublisher: AnyPublisher<String, Never> {
        $city
            .compactMap { $0.name }
            .eraseToAnyPublisher()
    }

    var locationWeather: Weather? { weather }

    var weatherRecievedPublisher: AnyPublisher<Bool, Never> {
        $isWeatherRecieved.eraseToAnyPublisher()
    }

    var temperaturePublisher: AnyPublisher<Temperature, Never> {
        $weather.combineLatest($temperatureUnits)
            .compactMap { weather, units in
                guard let currentWeather = weather?.current else { return nil }
                return (currentWeather, units)
            }
            .flatMap { currentWeather, units in
                Just(currentWeather).mapToTemperature(in: units)
            }
            .eraseToAnyPublisher()
    }

    var conditionPublisher: AnyPublisher<String, Never> {
        $weather
            .compactMap { $0?.current?.condition?.text }
            .eraseToAnyPublisher()
    }

    var weatherIconPublisher: AnyPublisher<(code: Int, dayPeriod: DayPeriod), Never> {
        weatherCodePublisher.combineLatest(dayPeriodPublisher)
            .map { ($0, $1) }
            .eraseToAnyPublisher()
    }

    var lowTemperaturePublisher: AnyPublisher<Temperature, Never> {
        $weather
            .compactMap { $0?.forecast?.forecastday?.first?.day }
            .mapToTemperature(type: .low, in: temperatureUnits)
            .eraseToAnyPublisher()
    }

    var highTemperaturePublisher: AnyPublisher<Temperature, Never> {
        $weather
            .compactMap { $0?.forecast?.forecastday?.first?.day }
            .mapToTemperature(type: .high, in: temperatureUnits)
            .eraseToAnyPublisher()
    }

    private var weatherCodePublisher: AnyPublisher<Int, Never> {
        $weather
            .compactMap { $0?.current?.condition?.code }
            .eraseToAnyPublisher()
    }

    private var dayPeriodPublisher: AnyPublisher<DayPeriod, Never> {
        $weather
            .compactMap { $0?.current?.isDay }
            .compactMap { DayPeriod(rawValue: $0) }
            .eraseToAnyPublisher()
    }

    private var cancellables = Set<AnyCancellable>()

    private var apiService: WeatherAPIService

    // MARK: - Initialization

    init(city: City, apiService: WeatherAPIService, temperatureUnits: TemperatureUnits) {
        self.city = city
        self.apiService = apiService
        self.temperatureUnits = temperatureUnits
    }

    // MARK: - Methods

    func getWeather() {
        guard let coordinate = city.coordinate else { return }

        do {
            let forecastPublisher = try apiService.getForecast(coordinate: coordinate)

            forecastPublisher
                .receive(on: DispatchQueue.main)
                .sink { completion in
                    if case .failure(let error) = completion {
                        print(error.localizedDescription)
                        self.isWeatherRecieved = false
                    }
                } receiveValue: { [weak self] weather in
                    self?.weather = weather
                    self?.isWeatherRecieved = true
                }
                .store(in: &cancellables)
        } catch {
            print(error.localizedDescription)
        }
    }
}

class LocationCollectionViewCell: BaseCollectionViewCell, ViewModelable {
    typealias ViewModel = LocationCollectionViewCellViewModel

    // MARK: Properties

    var viewModel: ViewModel! {
        didSet {
            setBindings()
            viewModel.getWeather()
        }
    }

    var isAllowsSelection = false
    private var cancellables = Set<AnyCancellable>()

    // MARK: - Views

    private lazy var mainVStack = UIStackView(
        arrangedSubviews: [
            locationLabel,
            mainHStack,
            conditionLabel,
            lowHighTemperatureHStack
        ],
        axis: .vertical,
        spacing: 10,
        alignment: .leading
    )

    private lazy var mainHStack = UIStackView(
        arrangedSubviews: [temperatureLabel, weatherIconImageView],
        axis: .horizontal,
        spacing: 20
    )

    private lazy var lowHighTemperatureHStack = UIStackView(
        arrangedSubviews: [lowTemperatureLabel, highTemperatureLabel],
        axis: .horizontal,
        spacing: 10
    )

    private lazy var locationLabel = UILabel(
        font: Resources.Fonts.system(size: 19, weight: .bold)
    )

    private lazy var temperatureLabel = UILabel(
        font: Resources.Fonts.system(size: 25, weight: .bold)
    )

    private lazy var weatherIconImageView = UIImageView(contentMode: .scaleAspectFit)

    private lazy var conditionLabel = UILabel(
        textColor: .lightText,
        font: Resources.Fonts.system(size: 14, weight: .medium)
    )

    private lazy var lowTemperatureLabel = UILabel(
        textColor: Resources.Colors.darkText,
        font: Resources.Fonts.system(size: 14),
        textAlignment: .left
    )

    private lazy var highTemperatureLabel = UILabel(
        textColor: Resources.Colors.darkText,
        font: Resources.Fonts.system(size: 14),
        textAlignment: .left
    )

    // MARK: - Layout

    override func layoutSubviews() {
        layer.cornerRadius = frame.height / 10
        addShadow()
    }

    // MARK: - Methods

    override func configureAppearance() {
        backgroundColor = Resources.Colors.secondaryBackground
    }

    override func setupViews() {
        contentView.addSubview(locationLabel, useAutoLayout: true)
        contentView.addSubview(mainVStack, useAutoLayout: true)
    }

    override func constraintViews() {
        lowTemperatureLabel.setContentHuggingPriority(.required, for: .horizontal)
        NSLayoutConstraint.activate([
            //            locationLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
//            locationLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
//            locationLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
//
            weatherIconImageView.heightAnchor.constraint(equalToConstant: 35),
            weatherIconImageView.widthAnchor.constraint(equalTo: weatherIconImageView.heightAnchor),

            mainVStack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
            mainVStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            mainVStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            mainVStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20)
//
//            mainVStack.topAnchor.constraint(greaterThanOrEqualTo: locationLabel.bottomAnchor, constant: 20),
//            mainVStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
//            mainVStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
//            mainVStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20)
        ])
    }
}

// MARK: - Private methods

private extension LocationCollectionViewCell {
    func setBindings() {
        viewModel.weatherRecievedPublisher
            .sink { [weak self] isRecieved in
                self?.isAllowsSelection = isRecieved
                self?.updateUI(isWeatherRecieved: isRecieved)
            }
            .store(in: &cancellables)

        viewModel.temperaturePublisher
            .map { $0.stringFormat }
            .assignToTextOnLabel(temperatureLabel)
            .store(in: &cancellables)

        viewModel.locationNamePublisher
            .assignToTextOnLabel(locationLabel)
            .store(in: &cancellables)

        viewModel.weatherIconPublisher
            .sink { [weak self] code, dayPeriod in
                self?.weatherIconImageView.image = Resources.Images.Weather.weatherIcon(
                    code: code,
                    dayPriod: dayPeriod
                )
            }
            .store(in: &cancellables)

        viewModel.lowTemperaturePublisher
            .map { $0.stringFormat }
            .sink { [weak self] in
                self?.lowTemperatureLabel.text = "L: \($0)"
            }
            .store(in: &cancellables)

        viewModel.highTemperaturePublisher
            .map { $0.stringFormat }
            .sink { [weak self] in
                self?.highTemperatureLabel.text = "H: \($0)"
            }
            .store(in: &cancellables)

        viewModel.conditionPublisher
            .assignToTextOnLabel(conditionLabel)
            .store(in: &cancellables)
    }

    func updateUI(isWeatherRecieved: Bool) {
        UIView.animate(withDuration: 0.5) { [weak self] in
            self?.mainVStack.alpha = isWeatherRecieved ? 1 : 0
        }
    }

    func addShadow() {
        layer.shadowRadius = 5
        layer.shadowOpacity = 0.6
        layer.shadowPath = UIBezierPath(
            roundedRect: CGRect(
                x: 0,
                y: 5,
                width: frame.width,
                height: frame.height
            ),
            cornerRadius: layer.cornerRadius
        ).cgPath
    }
}
